/*
 * Copyright (C) 2014-2016 Qiujuer <qiujuer@live.cn>
 * WebSite http://www.qiujuer.net
 * Author qiujuer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.qiujuer.genius.ui.drawable;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.Component;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.ColorFilter;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.miscservices.timeutility.Time;

/**
 * A drawable to draw loading
 * The loading draw a Circle
 *
 * @since 2021-05-13
 */
@SuppressWarnings("WeakerAccess")
public abstract class LoadingDrawable extends ShapeElement implements Animatable, Component.DrawTask {
    /**
     * 刷新绘制回调接口
     *
     * @since 2021-05-13
     */
    public interface Callback {
        public void invalidate();
    }

    private static final int LINE_SIZE = 4;
    protected Paint mForegroundPaint = new Paint();
    protected Paint mBackgroundPaint = new Paint();
    private Callback mCallback;
    private AnimatorValue mRenderAnimator;
    private boolean mRun;
    private int[] mForegroundColor = new int[]{0xcc000000, 0xfffe7865, 0xff842398};
    private int mForegroundColorIndex = 0;
    private static final long DEFAULT_FRAME_DELAY = 10L;
    private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
    private static final long ANIMATION_DURATION = 1333L;
    protected float mProgress;
    private final AnimatorValue.ValueUpdateListener mAnimatorUpdateListener
        = new AnimatorValue.ValueUpdateListener() {
        @Override
        public void onUpdate(AnimatorValue animatorValue, float v) {
            onRefresh();
            invalidateSelf();
        }
    };

    /**
     * 构造函数
     */
    public LoadingDrawable() {
        final Paint bPaint = mBackgroundPaint;
        bPaint.setStyle(Paint.Style.STROKE_STYLE);
        bPaint.setAntiAlias(true);
        bPaint.setDither(true);
        bPaint.setStrokeWidth(LINE_SIZE);
        bPaint.setColor(new Color(0x32000000));

        final Paint fPaint = mForegroundPaint;
        fPaint.setStyle(Paint.Style.STROKE_STYLE);
        fPaint.setAntiAlias(true);
        fPaint.setDither(true);
        fPaint.setStrokeWidth(LINE_SIZE);
        fPaint.setColor(new Color(mForegroundColor[0]));
        setupAnimators();
    }

    @Override
    public int getWidth() {
        float maxLine = Math.max(mBackgroundPaint.getStrokeWidth(), mForegroundPaint.getStrokeWidth());
        return (int) (maxLine * 2);
    }

    @Override
    public int getHeight() {
        float maxLine = Math.max(mBackgroundPaint.getStrokeWidth(), mForegroundPaint.getStrokeWidth());
        return (int) (maxLine * 2);
    }

    /**
     * 设置背景线条宽度
     *
     * @param size 宽度
     */
    public void setBackgroundLineSize(float size) {
        mBackgroundPaint.setStrokeWidth(size);
        setBounds(getBounds());
    }

    /**
     * 設置回調
     *
     * @param mCallback 回調
     */
    protected void setmCallback(Callback mCallback) {
        this.mCallback = mCallback;
    }

    /**
     * 設置前景线条寬度
     *
     * @param size 寬度
     */
    public void setForegroundLineSize(float size) {
        mForegroundPaint.setStrokeWidth(size);
        setBounds(getBounds());
    }

    /**
     * 获取背景线条宽度
     *
     * @return 返回宽度
     */
    public float getBackgroundLineSize() {
        return mBackgroundPaint.getStrokeWidth();
    }

    /**
     * 获取前景线条宽度
     *
     * @return 返回宽度
     */
    public float getForegroundLineSize() {
        return mForegroundPaint.getStrokeWidth();
    }

    /**
     * 设置背景颜色值
     *
     * @param color 颜色值
     */
    public void setBackgroundColor(int color) {
        mBackgroundPaint.setColor(new Color(color));
    }

    /**
     * 获取背景颜色值
     *
     * @return 颜色值
     */
    public int getBackgroundColor() {
        return mBackgroundPaint.getColor().getValue();
    }

    /**
     * 设置前景颜色值集合
     *
     * @param colors 颜色值集合
     */
    public void setForegroundColor(int[] colors) {
        if (colors == null)
            return;
//        this.mForegroundColor = colors;
        this.mForegroundColor = new int[colors.length];
        for (int i = 0; i < mForegroundColor.length; i++) {
            mForegroundColor[i] = colors[i];
        }
        this.mForegroundColorIndex = -1;
        getNextForegroundColor();
    }

    /**
     * 获取颜色值集合
     *
     * @return 颜色值集合
     */
    public int[] getForegroundColor() {
        int[] mForegroundColorTemp = new int[mForegroundColor.length];
        for (int i = 0; i < mForegroundColorTemp.length; i++) {
            mForegroundColorTemp[i] = mForegroundColor[i];
        }
        return mForegroundColorTemp;
    }

    int getNextForegroundColor() {
        final int[] colors = mForegroundColor;
        final Paint fPaint = mForegroundPaint;
        if (colors.length > 1) {
            int index = mForegroundColorIndex + 1;
            if (index >= colors.length)
                index = 0;
            fPaint.setColor(new Color(colors[index]));
            mForegroundColorIndex = index;
        } else {
            fPaint.setColor(new Color(colors[0]));
        }
        return fPaint.getColor().getValue();
    }

    /**
     * Get the loading progress
     *
     * @return Progress
     */
    public float getProgress() {
        return mProgress;
    }

    /**
     * Set the draw progress
     * The progress include 0~1 float
     * On changed, stop animation draw
     *
     * @param progress Loading progress
     */
    public void setProgress(float progress) {
        if (progress < 0)
            mProgress = 0;
        else if (mProgress > 1)
            mProgress = 1;
        else
            mProgress = progress;
        stop();
        onProgressChange(mProgress);
        invalidateSelf();
    }

    /**
     * 获取高度
     *
     * @return 高度
     */
    public abstract int getIntrinsicHeight();

    /**
     * 获取宽度
     *
     * @return 宽度
     */
    public abstract int getIntrinsicWidth();

    @Override
    public void onDraw(Component component, Canvas canvas) {
        drawToCanvas(canvas);
    }

    private void invalidateSelf() {
        mCallback.invalidate();
    }

    public boolean isRunning() {
        return mRun;
    }

    /**
     * 重置数据
     */
    protected abstract void reset();

    /**
     * 开始
     */
    public void start() {
        if (!mRun) {
            mRun = true;
            startAnimator();
        }
    }

    /**
     * 结束
     */
    public void stop() {
        if (mRun) {
            mRun = false;
            stopAnimator();
        }
    }

    protected void addRenderListener(AnimatorListenerAdapter listener) {
        mRenderAnimator.setLoopedListener(listener);
        mRenderAnimator.setStateChangedListener(listener);
    }

    void startAnimator() {
        reset();
        mRenderAnimator.setValueUpdateListener(mAnimatorUpdateListener);
        mRenderAnimator.setLoopedCount(AnimatorValue.INFINITE);
        mRenderAnimator.setDuration(ANIMATION_DURATION);
        mRenderAnimator.start();
    }

    private long getDuration(long when) {
        final long frameDelay = sFrameDelay;
        final long delay = when - Time.getRealActiveTime();
        return delay <= frameDelay ? 0 : delay - frameDelay;
    }

    void stopAnimator() {
        // if I just call mRenderAnimator.end(),
        // it will always call the method onAnimationUpdate(ValueAnimator animation)
        // why ? if you know why please send email to me (dinus_developer@163.com)
        mRenderAnimator.setValueUpdateListener(null);
        mRenderAnimator.setLoopedCount(0);
        mRenderAnimator.setDuration(0);
        mRenderAnimator.stop();
    }

    private void setupAnimators() {
        mRenderAnimator = new AnimatorValue();
        mRenderAnimator.setLoopedCount(AnimatorValue.INFINITE);
        mRenderAnimator.setDuration(ANIMATION_DURATION);

        // the default interpolator is AccelerateDecelerateInterpolator
        mRenderAnimator.setCurveType(AnimatorValue.CurveType.LINEAR);
        mRenderAnimator.setValueUpdateListener(mAnimatorUpdateListener);
    }

    @Override
    public void drawToCanvas(Canvas canvas) {
        super.drawToCanvas(canvas);
        int count = canvas.save();

        final Paint bPaint = mBackgroundPaint;
        if (bPaint.getColor().getValue() != 0 && bPaint.getStrokeWidth() > 0) {
            drawBackground(canvas, bPaint);
        }
        final Paint fPaint = mForegroundPaint;
        if (mRun) {
            if (fPaint.getColor().getValue() != 0 && fPaint.getStrokeWidth() > 0) {
                drawForeground(canvas, fPaint);
            }
        } else if (mProgress > 0) {
            if (fPaint.getColor().getValue() != 0 && fPaint.getStrokeWidth() > 0) {
                drawForeground(canvas, fPaint);
            }
        }
        canvas.restoreToCount(count);
    }

    @Override
    public void setAlpha(int alpha) {
        mForegroundPaint.setAlpha(alpha);
    }

    /**
     * 更新显示范围
     *
     * @param left 左边
     * @param top 上边
     * @param right 右边
     * @param bottom 下边
     */
    public abstract void onBoundsChange(int left, int top, int right, int bottom);

    /**
     * 设置颜色过滤
     *
     * @param cf 颜色过滤值
     */
    public void setColorFilter(ColorFilter cf) {
        boolean needRefresh = false;
        final Paint bPaint = mBackgroundPaint;
        if (bPaint.getColorFilter() != cf) {
            bPaint.setColorFilter(cf);
            needRefresh = true;
        }

        final Paint fPaint = mForegroundPaint;
        if (fPaint.getColorFilter() != cf) {
            fPaint.setColorFilter(cf);
            needRefresh = true;
        }

        if (needRefresh) {
            invalidateSelf();
        }
    }

    @Override
    public int getAlpha() {
        final Paint bPaint = mBackgroundPaint;
        final Paint fPaint = mForegroundPaint;
        if (bPaint.getBlendMode() == null && fPaint.getBlendMode() == null) {
            final int alpha = Color.alpha(fPaint.getColor().getValue());
            if (alpha == 0) {
                return 0;
            }
            if (alpha == 255) {
                return 255;
            }
        }

        // not sure, so be safe
        return 255 / 2;
    }

    /**
     * 刷新数据
     */
    protected abstract void onRefresh();

    /**
     * 画背景
     *
     * @param canvas 画布
     * @param backgroundPaint 画笔
     */
    protected abstract void drawBackground(Canvas canvas, Paint backgroundPaint);

    /**
     * 画前景
     *
     * @param canvas 画布
     * @param foregroundPaint 画笔
     */
    protected abstract void drawForeground(Canvas canvas, Paint foregroundPaint);

    /**
     * 更新进度值
     *
     * @param progress 滚动值
     */
    protected abstract void onProgressChange(float progress);

    /**
     * 动画监听
     *
     * @since 2021-05-13
     */
    private static class AnimatorListenerAdapter implements Animator.LoopedListener, Animator.StateChangedListener {
        @Override
        public void onRepeat(Animator animator) {
        }

        @Override
        public void onStart(Animator animator) {
        }

        @Override
        public void onStop(Animator animator) {
        }

        @Override
        public void onCancel(Animator animator) {
        }

        @Override
        public void onEnd(Animator animator) {
        }

        @Override
        public void onPause(Animator animator) {
        }

        @Override
        public void onResume(Animator animator) {
        }
    }
}